
package com.sensorviewera;

import android.os.Looper;
import android.os.Message;
import android.os.Handler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

class EventType
{
    public static final int NEW_SOCKET=0;
    public static final int TRANS_START=1; 
    public static final int TRANS_END=2;
    public static final int NEW_DATA=3;
    public static final int NEW_MSG=4;
    public static final int HEARTBEAT_CLOSE=5;
    public static final int CONNECT_FAIL=6;
}
class TimeConst
{
    public static final int CONNECT_OUT_MS=2000; 
    public static final int READ_OUT_MS=250; 
    public static final int INTERNAL_TIME_MS=300;
    public static final int TIMER_INTERNAL=10;
    public static final long TIME_WAIT_FOR_EXIT_MS =2000; 
    public static final int TRY_RECON_MS =3000;
}
public class DataTrans
{
    private SendData send_thread=null;
    private Heartbeat heartbeat_thread=null;
    private String remote_ip=null;
    private int remote_port=0;
    private int next;

    public DataTrans(Handler notify)
    {
        BaseDataTrans.setNotify(notify);
        send_thread=new SendData();
        heartbeat_thread=new Heartbeat();
        next=1;
    }
    public void startTrans(String ip,int port)
    {
        remote_ip=ip;
        remote_port = port;
        heartbeat_thread.goToWork(remote_ip,remote_port,0);
        next++;
    }
    public void restartTrans(int delay_con_ms)
    {
        if(remote_ip==null)return;
        if(next==3){next=1;return;}
        switch (next)
        {
            case 1:
                heartbeat_thread.goToWork(remote_ip,remote_port,delay_con_ms);
                next++;
                break;
            case 2:
                send_thread.goToWork(remote_ip,remote_port,0);
                next++;
                break;
            default:
                break;
        }
    }
    public Handler getSender(){return send_thread.getHandler();}
    public void reset()
    {
        heartbeat_thread.stopWork();
        heartbeat_thread.closeSock();
        send_thread.closeSock();
        send_thread.removeAllWork();
        next=1;
    }
    public void close()
    {
        reset();
        send_thread.quit();
        heartbeat_thread.quit();
    }
}
class BaseDataTrans
{
    protected LoopThread worker=null;
    protected Handler msg_handler=null;
    protected GetSocket get_sock=null;
    protected Runnable close_sock=null; 

    protected Socket sock=null;
    protected OutputStream output=null;
    protected InputStream input=null;
    protected boolean connected=false; 

    protected Message msg_to_ui =null;
    protected static Handler notify;
    public static void setNotify(Handler h){notify=h;}
    public BaseDataTrans()
    {
        worker=new LoopThread();
        close_sock=new Runnable()
        {
            @Override
            public void run()
            {
                if(sock!=null&&!sock.isClosed())
                {
                    try {sock.close();}
                    catch (IOException e) {}
                }
                sock=null;
                connected=false;
                input=null;
                output=null;
            }
        };
    }
    protected void notifyUI(int type,Object obj)
    {
        msg_to_ui = Message.obtain();
        msg_to_ui.what=type;
        msg_to_ui.obj=obj;
        notify.sendMessage(msg_to_ui); 
    }
    protected void handleNewSock(Message msg)
    {
        if(msg.obj==null)
        {
            connected=false;
            sock=null;
            input=null;
            output=null;
            notifyUI(EventType.CONNECT_FAIL,null);
            return;
        }
        connected=true;
        sock=(Socket)msg.obj;
        try
        {
            input=sock.getInputStream();
            output=sock.getOutputStream();
        }
        catch (IOException e)
        {
            connected=false;
            sock=null;
            input=null;
            output=null;
            try {sock.close();}
            catch (IOException e1) {}
            notifyUI(EventType.CONNECT_FAIL,null);
            return;
        }
        notifyUI(EventType.TRANS_START,null);
    }
    protected void closeSock(){msg_handler.postAtFrontOfQueue(close_sock);}
    protected void goToWork(String remote_ip,int remote_port,int delay_con_ms)
    {
        get_sock.getSocket(remote_ip,remote_port,delay_con_ms);
    }
    protected Handler getHandler(){return msg_handler;}
    protected void quit(){worker.quit();}
}
class SendData extends BaseDataTrans
{
    public SendData()
    {
        super();
        msg_handler=new SendHandler(worker.getLooper());
        get_sock=new GetSocket(msg_handler);
    }
    class SendHandler extends Handler
    {
        public SendHandler(Looper l){super(l);}
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case EventType.NEW_DATA:
                    if(!connected)break;
                    try{output.write((byte[])msg.obj);}
                    catch (IOException e)
                    {
                        try {sock.close();}
                        catch (IOException e1) {}
                        notifyUI(EventType.TRANS_END,null);
                    }
                    break;
                case EventType.NEW_SOCKET:
                    handleNewSock(msg);
                    break;
                default:
                    break;
            }
        }
    }
    public void removeAllWork()
    {
        msg_handler.removeMessages(EventType.NEW_DATA);
    }
}
class Heartbeat extends BaseDataTrans
{
    private static final int BUFFER_SIZE=4;
    private int timer_internal=0;
    private boolean stoped=false;
    private static final String ASK_STR="HTBT";
    private byte[] buffer=new byte[BUFFER_SIZE];
    private Runnable heartbeat=null;
    public Heartbeat()
    {
        super();
        msg_handler=new HeartbeatHandler(worker.getLooper());
        get_sock=new GetSocket(msg_handler);
        heartbeat=new Runnable()
        {
            @Override
            public void run()
            {
                if(stoped)return;
                if(++timer_internal<=TimeConst.TIMER_INTERNAL)
                {
                    try
                    {
                        input.read(buffer,0,BUFFER_SIZE);
                        notifyUI(EventType.NEW_MSG,new String(buffer));
                    }
                    catch (IOException e) {}
                    msg_handler.postDelayed(heartbeat,TimeConst.INTERNAL_TIME_MS);
                    return;
                }
                try{output.write(ASK_STR.getBytes());}
                catch (IOException e)
                {
                    timer_internal=0;
                    try {sock.close();}
                    catch (IOException e1) {}
                    notifyUI(EventType.HEARTBEAT_CLOSE,null);
                    return;
                }
                timer_internal=0;
                msg_handler.post(heartbeat);
            }
        };
    }
    class HeartbeatHandler extends Handler
    {
        public HeartbeatHandler(Looper l){super(l);}
        @Override
        public void handleMessage(Message msg)
        {
            if(msg.what==EventType.NEW_SOCKET)
                handleNewSock(msg);
            if(connected)
            {
                stoped=false;
                msg_handler.post(heartbeat);
            }
        }
    }
    public void stopWork(){stoped=true;}
}
class GetSocket implements Runnable
{
    private Handler handler=null;
    private int remote_port=0; 
    private InetSocketAddress remote_addr=null; 

    public GetSocket(Handler h){handler=h;}
    @Override
    public void run()
    {
        Message msg=Message.obtain();
        msg.what=EventType.NEW_SOCKET;
        Socket sock=new Socket();
        try
        {
            sock.connect(remote_addr,TimeConst.CONNECT_OUT_MS); 
            sock.setTcpNoDelay(true);
            sock.setSoTimeout(TimeConst.READ_OUT_MS);
            msg.obj=sock;
        }
        catch (IOException e){msg.obj=null;}
        handler.sendMessage(msg);
    }
    public void getSocket(String ip, int port,int dcm)
    {
        handler.removeCallbacks(this);
        remote_port=port;
        try{remote_addr=new InetSocketAddress(InetAddress.getByName(ip),remote_port );}
        catch (UnknownHostException e) {return;}
        handler.postDelayed(this,dcm);
    }
}

class LoopThread implements Runnable
{
    private Looper my_looper=null;
    private final Object lock=new Object();
    public LoopThread()
    {
        Thread run_self = new Thread(this);
        run_self.setPriority(Thread.MAX_PRIORITY);
        run_self.start();
        synchronized (lock)
        {
            while (my_looper == null)
            {
                try {lock.wait();}
                catch (InterruptedException ex) {}
            }
        }
    }
    @Override
    public void run()
    {
        synchronized (lock)
        {
            Looper.prepare();
            my_looper = Looper.myLooper();
            lock.notifyAll();
        }
        Looper.loop();
    }
    public void quit()
    {my_looper.quit();}
    public Looper getLooper()
    {return my_looper;}
}




